package org.fsdeduplicator;

import java.awt.*;
import java.awt.event.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Formatter;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;

public class FileDedupeFrame extends JFrame {

    private static final Dimension APP_SIZE = new Dimension(800, 600);
    private static final Dimension MIN_APP_SIZE = new Dimension(400, 300);
    private static final Insets CONTROL_PANE_MARGIN = new Insets(10, 10, 10, 10);
    private static final Insets INFO_PANE_MARGIN = new Insets(10, 10, 10, 20);
    private static final JLabel DEDUPE_LABEL = new JLabel("Dedupe options: ");
    private static final Dimension BUTTONS_SIZE = new Dimension(94, 30);
    
    private static final String INITIAL_PATH_CAPTION = "Press Ctrl-O to open new location...";
    private static final String OPENED_CAPTION = "Press 'Start' to run File Dedupe...";
    private static final String WAITING_CAPTION = "Please wait...";
    private static final String DIRECTORY_CAPTION = "Root directory: ";
    private static final String STEP_ONE_CAPTION = "Step 1: Filtering ";
    private static final String STEP_TWO_CAPTION = "Step 2: Processing ";

    private static final String DONE_CAPTION = "<html><font color=#00CC00>Done</font></html>";
    private static final String STOPPED_CAPTION = "<html><font color=#FF0000>Stopped</font></html>";
    
    private JLabel pathLabel = new JLabel(INITIAL_PATH_CAPTION);
    private JLabel processLabel = new JLabel("");
    private JProgressBar progressBar = new JProgressBar(0);

    private FileDedupe fileDedupe = null;
    private NavigationMenu navigationMenu = new NavigationMenu(this);

    private JScrollPane scrollPane;
    private DefaultTableModel tableModel = new DefaultTableModel();
    private String rootLocation = null;
    private int pairsCount = 0;
    private int processedCount = 0;

    public FileDedupeFrame() {
        super("File System Deduplicator");
        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        addWindowListener(new CloseListener());

        // navigation menu bar
        setJMenuBar(navigationMenu.menuBar);

        // information on the top
        getContentPane().add(createInfoPane(), "North");

        // right side control bar (which is connected with the navigation menu by actions)
        getContentPane().add(createControlBar(navigationMenu), "East");

        // files table
        // will be created on start
        scrollPane = createFilesTable();
        scrollPane.setVisible(false);
        getContentPane().add(scrollPane);

        // starting

        setBounds(Toolkit.getDefaultToolkit().getScreenSize().width / 2 - APP_SIZE.width / 2,
                Toolkit.getDefaultToolkit().getScreenSize().height / 2 - APP_SIZE.height / 2,
                APP_SIZE.width, APP_SIZE.height);

        setMinimumSize(MIN_APP_SIZE);
        setVisible(true);
    }

    private JToolBar createInfoPane() {
        JToolBar infoPane = new JToolBar(0);
        infoPane.setLayout(new GridLayout(1, 2, 0, 0));
        infoPane.setFloatable(false);
        infoPane.setMargin(INFO_PANE_MARGIN);
        infoPane.setBackground(Color.WHITE);

        JPanel leftInfo = new JPanel(new GridLayout(2, 1, 0, 5));
        leftInfo.setBackground(Color.WHITE);
        leftInfo.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
        leftInfo.add(pathLabel, 0);
        leftInfo.add(processLabel, 1);

        progressBar.setBorderPainted(false);
        progressBar.setVisible(false);

        JPanel rightInfo = new JPanel(new BorderLayout());
        rightInfo.setBackground(Color.WHITE);
        rightInfo.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
        rightInfo.add(progressBar, "Center");

        infoPane.add(leftInfo);
        infoPane.add(rightInfo);

        return infoPane;
    }

    private JScrollPane createFilesTable() {
        tableModel.setColumnIdentifiers(new String[]{"File", "File", "Similarity"});

        final JTable filesTable = new JTable(tableModel);
        filesTable.setEnabled(false);
        filesTable.addMouseListener(new FilesTableRClickListener(filesTable));

        JScrollPane tableScroll = new JScrollPane(filesTable,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        tableScroll.setBorder(BorderFactory.createEmptyBorder());
        tableScroll.setWheelScrollingEnabled(true);

        return tableScroll;
    }

    private JToolBar createControlBar(NavigationMenu navigationMenu) {
        JToolBar controlPane = new JToolBar(1);
        controlPane.setLayout(new BoxLayout(controlPane, BoxLayout.Y_AXIS));
        controlPane.setFloatable(false);
        controlPane.setRollover(true);
        controlPane.setMargin(CONTROL_PANE_MARGIN);
        controlPane.setMinimumSize(new Dimension(400, 10));

        JButton startButton = new JButton(navigationMenu.startAction);
        JButton pauseButton = new JButton(navigationMenu.pauseAction);
        JButton stopButton = new JButton(navigationMenu.stopAction);

        startButton.setMaximumSize(BUTTONS_SIZE);
        pauseButton.setMaximumSize(BUTTONS_SIZE);
        stopButton.setMaximumSize(BUTTONS_SIZE);

        startButton.setFocusable(false);
        pauseButton.setFocusable(false);
        stopButton.setFocusable(false);

        controlPane.add(DEDUPE_LABEL);
        controlPane.addSeparator();
        controlPane.add(startButton);
        controlPane.addSeparator();
        controlPane.add(pauseButton);
        controlPane.addSeparator();
        controlPane.add(stopButton);

        return controlPane;
    }

    void setPairsCount(int pairsCount, boolean isFiltering) {
        this.pairsCount = pairsCount;
        processedCount = 0;

        processLabel.setText(isFiltering ? STEP_ONE_CAPTION : STEP_TWO_CAPTION
                + processedCount + " / " + pairsCount + " pairs");

        progressBar.setMinimum(0);
        progressBar.setMaximum(pairsCount);
        progressBar.setValue(0);
        progressBar.setVisible(true);
    }

    void addTableRow(String[] columns) {
        if (!scrollPane.isVisible()) {
            scrollPane.setVisible(true);
        }
        
        columns[0] = columns[0].replace(rootLocation, "");
        columns[1] = columns[1].replace(rootLocation, "");

        // TODO: mark row if similarity is 100%

        tableModel.addRow(columns);
        increaseProcessedCount(false);
    }

    void increaseProcessedCount(boolean isFiltering) {
        processedCount++;

        if (isFiltering) {
            processLabel.setText(STEP_ONE_CAPTION + processedCount + " / " + pairsCount + " pairs");
        } else {
            processLabel.setText(STEP_TWO_CAPTION + processedCount + " / " + pairsCount + " pairs");
        }

        progressBar.setValue(processedCount);
    }

    void setRootLocation(String rootLocation) {
        if (fileDedupe != null) {
            fileDedupe.interrupt();

            try {
                fileDedupe.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.rootLocation = rootLocation;

        navigationMenu.startAction.setEnabled(true);
        navigationMenu.pauseAction.setEnabled(false);
        navigationMenu.stopAction.setEnabled(false);

        pathLabel.setText(DIRECTORY_CAPTION + rootLocation);
        processLabel.setText(OPENED_CAPTION);
        navigationMenu.pauseAction.putValue(Action.NAME, "Pause");

        scrollPane.setVisible(false);
    }

    public void reportError() {
        final JFrame parentFrame = this;

        // preparing new thread to display a message
        Thread messageThread = new Thread() {
            @Override
            public void run() {
                JOptionPane.showMessageDialog(parentFrame, "An error occurred while operating with I/O",
                        "Error", JOptionPane.ERROR_MESSAGE);
            }
        };

        messageThread.start();
    }

    void performStart() {
        if (rootLocation != null) {
            try {
                fileDedupe = new FileDedupe(this, rootLocation);
            } catch (IOException e) {
                reportError();
                return;
            }

            for (int i = tableModel.getRowCount() - 1; i >= 0; i--) {
                tableModel.removeRow(i);
            }
        }

        fileDedupe.start();
        processLabel.setText(WAITING_CAPTION);

        navigationMenu.startAction.setEnabled(false);
        navigationMenu.pauseAction.setEnabled(true);
        navigationMenu.stopAction.setEnabled(true);

        scrollPane.setVisible(false);
    }

    void performPause() {
        fileDedupe.changePausedState();
        if (fileDedupe.isPaused()) {
            navigationMenu.pauseAction.putValue(Action.NAME, "Resume");
        } else {
            navigationMenu.pauseAction.putValue(Action.NAME, "Pause");
        }
    }

    void performStop() {
        if (fileDedupe != null) {
            fileDedupe.interrupt();
        }

        navigationMenu.pauseAction.putValue(Action.NAME, "Pause");
    }

    void finish(boolean isNormal) {
        if (isNormal) {
            processLabel.setText(DONE_CAPTION);
        } else {
            processLabel.setText(STOPPED_CAPTION);
        }

        pairsCount = 0;
        processedCount = 0;

        progressBar.setMaximum(0);
        progressBar.setValue(0);
        progressBar.setVisible(false);

        navigationMenu.startAction.setEnabled(true);
        navigationMenu.pauseAction.setEnabled(false);
        navigationMenu.stopAction.setEnabled(false);

        navigationMenu.pauseAction.putValue(Action.NAME, "Pause");
    }

    void doOnClose() {
        if (fileDedupe != null) {
            fileDedupe.interrupt();

            try {
                fileDedupe.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.exit(0);
    }

    private class CloseListener extends WindowAdapter {
        @Override
        public void windowClosing(WindowEvent event) {
            doOnClose();
        }
    }

    private class FilesTableRClickListener extends MouseAdapter {
        private JTable table;

        public FilesTableRClickListener(JTable table) {
            this.table = table;
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            JPopupMenu popupMenu = new JPopupMenu();
            Point p = e.getPoint();
            int row = table.rowAtPoint(p);
            int col = table.columnAtPoint(p);

            if (row != -1) {
                if (col == 2) {
                    String fileName1 = table.getValueAt(row, 0).toString();
                    String fileName2 = table.getValueAt(row, 1).toString();

                    JMenuItem viewDiff = new JMenuItem("View diff");
                    viewDiff.addActionListener(new ViewDiffActionListener(fileName1, fileName2));
                    popupMenu.add(viewDiff);
                } else {
                    String fileName = table.getValueAt(row, col).toString();

                    JMenuItem deleteFile = new JMenuItem("Delete file");
                    deleteFile.addActionListener(new DeleteFileActionListener(fileName, table));
                    popupMenu.add(deleteFile);
                }
            }
            popupMenu.show(table, e.getX(), e.getY());
        }
    }

    private class ViewDiffActionListener implements ActionListener {
        private String fileName1;
        private String fileName2;

        public ViewDiffActionListener(String fileName1, String fileName2) {
            this.fileName1 = fileName1;
            this.fileName2 = fileName2;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            try {
                Process diff = Runtime.getRuntime().exec(new String[]{"diff", rootLocation + fileName1, rootLocation + fileName2});
                BufferedReader diffBuffer = new BufferedReader(new InputStreamReader(diff.getInputStream()));

                JDialog dialog = new JDialog((Dialog) null, "Diff");
                dialog.setBounds(100, 100, 400, 300);

                JTextArea textArea = new JTextArea();
                textArea.setEditable(false);
                textArea.read(diffBuffer, null);

                JScrollPane scrollPane = new JScrollPane(textArea,
                        JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                        JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
                dialog.getContentPane().add(scrollPane);

                dialog.setVisible(true);
            } catch (IOException ex) {
                reportError();
            }
        }
    }

    private class DeleteFileActionListener implements ActionListener {
        private String fileName;
        private JTable table;

        private DeleteFileActionListener(String fileName, JTable table) {
            this.fileName = fileName;
            this.table = table;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            File file = new File(rootLocation + fileName);
            if (file.delete()) {
                for (int i = table.getRowCount() - 1; i >= 0; i--) {
                    if (table.getValueAt(i, 0).toString().equals(fileName) || table.getValueAt(i, 1).toString().equals(fileName)) {
                        ((DefaultTableModel)table.getModel()).removeRow(i);
                    }
                }
            }
        }
    }
}

